package repository

import (
	"context"
	"database/sql"
	"fmt"
	"strings"

	"mall/common/modelx"
	"mall/service/backend/model"

	"github.com/volatiletech/sqlboiler/v4/boil"
	"github.com/volatiletech/sqlboiler/v4/queries/qm"
	"github.com/zeromicro/go-zero/core/stores/cache"
)

type (
	// BackendUserRepository is an interface to be customized, add more methods here,
	// and implement the added methods in customBackendUserRepository.
	BackendUserRepository interface {
		Conn() error
		GetList(ctx context.Context, cols model.M, withCount int) ([]*model.BackendUser, int64, error)
		RetrieveByCredentialField(ctx context.Context, username string, field string, status int32) (*model.BackendUser, error)
		RetrieveById(ctx context.Context, id int64) (*model.BackendUser, error)
		RetrieveMenus(ctx context.Context, user *model.BackendUser, status int32) ([]*model.BackendMenu, error)
		Insert(ctx context.Context, user *model.BackendUser) error
		Update(ctx context.Context, user *model.BackendUser) (int64, error)
		ResetPassword(ctx context.Context, user *model.BackendUser) (int64, error)
		UpdateBatch(ctx context.Context, ids []int64, cols model.M) (int64, error)
		ForceDeleteBatch(ctx context.Context, ids []int64) (int64, error)
	}

	customBackendUserRepository struct {
		cacheConf  cache.CacheConf
		dataSource string

		mysqlConn *sql.DB
	}
)

// NewBackendUserRepository returns a repository for the database table.
func NewBackendUserRepository(dataSource string, cacheConf cache.CacheConf) BackendUserRepository {
	return &customBackendUserRepository{
		cacheConf:  cacheConf,
		dataSource: dataSource,
	}
}

func (r *customBackendUserRepository) Conn() error {
	if r.mysqlConn == nil {
		mysqlConn, err := modelx.NewMysqlConn(r.dataSource)
		if err != nil {
			return err
		}

		r.mysqlConn = mysqlConn
	}

	return nil
}

func (r *customBackendUserRepository) GetList(
	ctx context.Context,
	cols model.M,
	withCount int,
) (users []*model.BackendUser, count int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	var page int
	var pageSize int
	var selectCol string
	for k, v := range cols {
		switch k {
		case model.BackendUserColumns.ID:
			if val, ok := v.(int64); ok && val > 0 {
				mods = append(mods, model.BackendUserWhere.ID.EQ(uint64(val)))
			}
		case model.BackendUserColumns.Username:
			if val, ok := v.(string); ok && val != "" {
				mods = append(mods, model.BackendUserWhere.Username.EQ(val))
			}
		case model.BackendUserColumns.Nickname:
			if val, ok := v.(string); ok && val != "" {
				mods = append(mods, model.BackendUserWhere.Nickname.EQ(val))
			}
		case model.BackendUserColumns.Name:
			if val, ok := v.(string); ok && val != "" {
				mods = append(mods, model.BackendUserWhere.Name.EQ(val))
			}
		case model.BackendUserColumns.Email:
			if val, ok := v.(string); ok && val != "" {
				mods = append(mods, model.BackendUserWhere.Email.EQ(val))
			}
		case model.BackendUserColumns.Mobile:
			if val, ok := v.(string); ok && val != "" {
				mods = append(mods, model.BackendUserWhere.Mobile.EQ(val))
			}
		case model.BackendUserColumns.Status:
			if val, ok := v.(int32); ok && val > 0 {
				mods = append(mods, model.BackendUserWhere.Status.EQ(uint16(val-1)))
			}
		case "page":
			if val, ok := v.(int64); ok && val > 1 {
				page = int(val)
			}
		case "pageSize":
			if val, ok := v.(int64); ok && val > 0 {
				pageSize = int(val)
			}
		case "orderBy":
			if val, ok := v.(string); ok && val != "" {
				mods = append(mods, qm.OrderBy(val))
			}
		case "ids":
			if val, ok := v.(string); ok && val != "" {
				ids := strings.Split(val, ",")
				if len(ids) > 0 {
					values := modelx.UniqueValues(ids, 64)
					mods = append(mods, qm.WhereIn(fmt.Sprintf("%s IN ?", model.BackendUserColumns.ID), values...))
				}
			}
		case "stati":
			if val, ok := v.(string); ok && val != "" {
				stati := strings.Split(val, ",")
				if len(stati) > 0 {
					values := modelx.UniqueValues(stati, 32)
					mods = append(mods, qm.WhereIn(fmt.Sprintf("%s IN ?", model.BackendUserColumns.Status), values...))
				}
			}
		case "select":
			if val, ok := v.(string); ok && val != "" {
				selectCol = val
			}
		}
	}

	if withCount >= 0 {
		count, err = model.BackendUsers(mods...).Count(ctx, r.mysqlConn)
		if err != nil {
			return
		}

		if withCount == 0 {
			return
		}
	}

	if pageSize > 0 {
		if page > 1 {
			mods = append(mods, qm.Offset(pageSize*(page-1)))
		}
		mods = append(mods, qm.Limit(pageSize))
	}

	var columns []string
	if selectCol != "" {
		if selectCol != "," {
			columns = strings.Split(selectCol, ",")
		}
		columns = append(columns, model.BackendUserColumns.ID)
	}
	if len(columns) > 0 {
		mods = append(mods, qm.Select(columns...))
	}

	users, err = model.BackendUsers(mods...).All(ctx, r.mysqlConn)
	return
}

func (r *customBackendUserRepository) RetrieveByCredentialField(
	ctx context.Context,
	username string,
	field string,
	status int32,
) (*model.BackendUser, error) { // #24.04.27# 去除
	if err := r.Conn(); err != nil {
		return nil, err
	}

	var mod qm.QueryMod
	switch field {
	case "email":
		mod = model.BackendUserWhere.Email.EQ(username)
	case "mobile":
		mod = model.BackendUserWhere.Mobile.EQ(username)
	default:
		mod = model.BackendUserWhere.Username.EQ(username)
	}

	// tx, err := r.mysqlConn.BeginTx(ctx, nil)
	// if err != nil {
	// 	return nil, err
	// }
	// user, err := models.BackendUsers().One(ctx, tx)

	user, err := model.BackendUsers(
		qm.Select(
			model.BackendUserColumns.ID,
			model.BackendUserColumns.Username,
			model.BackendUserColumns.Password,
			model.BackendUserColumns.Salt,
			model.BackendUserColumns.Name,
			model.BackendUserColumns.Email,
			model.BackendUserColumns.Mobile,
			model.BackendUserColumns.Phone,
		),
		mod,
		model.BackendUserWhere.Status.EQ(uint16(status)),
	).One(ctx, r.mysqlConn)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, model.ErrNotFound
		}

		return nil, err
	}

	return user, nil
}

func (r *customBackendUserRepository) RetrieveById(ctx context.Context, id int64) (*model.BackendUser, error) {
	if err := r.Conn(); err != nil {
		return nil, err
	}

	user, err := model.FindBackendUser(ctx, r.mysqlConn, uint64(id))
	if err != nil {
		return nil, err
	}

	return user, nil
}

func (r *customBackendUserRepository) RetrieveMenus(ctx context.Context, user *model.BackendUser, status int32) ([]*model.BackendMenu, error) {
	if err := r.Conn(); err != nil {
		return nil, err
	}

	var mods []qm.QueryMod
	mods = append(mods, model.BackendUserHasRolePivotWhere.UserID.EQ(uint64(user.ID)))
	roles, err := model.BackendUserHasRolePivots(mods...).All(ctx, r.mysqlConn)
	if err != nil {
		return nil, err
	}

	var roleIds []interface{}
	for _, role := range roles {
		roleIds = append(roleIds, role.RoleID)
	}

	var resp []*model.BackendMenu
	if len(roleIds) > 0 {
		var menuPivotMods []qm.QueryMod
		menuPivotMods = append(menuPivotMods, qm.WhereIn(model.BackendRoleHasMenuPivotColumns.RoleID+" IN ?", roleIds...))
		pivots, err := model.BackendRoleHasMenuPivots(menuPivotMods...).All(ctx, r.mysqlConn)
		if err != nil {
			return nil, err
		}

		var menuIds []interface{}
		for _, pivot := range pivots {
			menuIds = append(menuIds, pivot.MenuID)
		}

		if len(menuIds) > 0 {
			var menuMods []qm.QueryMod
			menuMods = append(menuMods, qm.WhereIn(model.BackendMenuColumns.MenuID+" IN ?", menuIds...))
			menus, err := model.BackendMenus(menuMods...).All(ctx, r.mysqlConn)
			if err != nil {
				return nil, err
			}

			menuMap := make(map[uint64]*model.BackendMenu)
			for _, menu := range menus {
				if menu.Status != uint8(status) {
					continue
				}

				if _, ok := menuMap[menu.MenuID]; !ok {
					menuMap[menu.MenuID] = menu
					resp = append(resp, menu)
				}
			}
		}
	}

	// roles, err := user.RoleBackendRoles(
	// 	qm.Load(model.BackendRoleRels.MenuBackendMenus),
	// 	model.BackendRoleWhere.Status.EQ(uint8(status)),
	// ).All(ctx, r.mysqlConn)
	// if err != nil {
	// 	return nil, err
	// }

	// menuMap := make(map[uint64]*model.BackendMenu)
	// var resp []*model.BackendMenu
	// for _, role := range roles {
	// 	menus := role.R.MenuBackendMenus
	// 	for _, menu := range menus {
	// 		if menu.Status != uint8(status) {
	// 			continue
	// 		}

	// 		if _, ok := menuMap[menu.MenuID]; !ok {
	// 			menuMap[menu.MenuID] = menu
	// 			resp = append(resp, menu)
	// 		}
	// 	}
	// }
	return resp, nil
}

func (r *customBackendUserRepository) Insert(ctx context.Context, user *model.BackendUser) (err error) {
	if err = r.Conn(); err != nil {
		return
	}

	err = user.Insert(ctx, r.mysqlConn, boil.Infer())
	return
}

func (r *customBackendUserRepository) Update(ctx context.Context, user *model.BackendUser) (userid int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	userid, err = user.Update(ctx, r.mysqlConn, boil.Blacklist(
		model.BackendUserColumns.Password,
		model.BackendUserColumns.Salt,
		model.BackendUserColumns.CreatedBy,
		model.BackendUserColumns.CreatedAt,
	))
	return
}

func (r *customBackendUserRepository) ResetPassword(ctx context.Context, user *model.BackendUser) (userid int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	userid, err = user.Update(ctx, r.mysqlConn, boil.Whitelist(
		model.BackendUserColumns.Password,
		model.BackendUserColumns.Salt,
		model.BackendUserColumns.LastUpdatedBy,
		model.BackendUserColumns.UpdatedAt,
	))
	return
}

func (r *customBackendUserRepository) UpdateBatch(ctx context.Context, ids []int64, cols model.M) (rowsAff int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	mods = append(mods, model.BackendUserWhere.IsSuper.EQ(0))
	if len(ids) > 0 {
		values := make([]interface{}, 0, len(ids))
		for _, id := range ids {
			values = append(values, uint64(id))
		}

		mods = append(mods, qm.WhereIn(fmt.Sprintf("%s IN ?", model.BackendUserColumns.ID), values...))
	}

	rowsAff, err = model.BackendUsers(mods...).UpdateAll(ctx, r.mysqlConn, cols)
	return
}

func (r *customBackendUserRepository) ForceDeleteBatch(ctx context.Context, ids []int64) (rowsAff int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	mods = append(mods, model.BackendUserWhere.IsSuper.EQ(0))
	if len(ids) > 0 {
		values := make([]interface{}, 0, len(ids))
		for _, id := range ids {
			values = append(values, uint64(id))
		}

		mods = append(mods, qm.WhereIn(fmt.Sprintf("%s IN ?", model.BackendUserColumns.ID), values...))
	}

	rowsAff, err = model.BackendUsers(mods...).DeleteAll(ctx, r.mysqlConn)
	return
}
